Building and Plotting a Custom Indicator in R

Learning R For Finance – Post 21

One of the most interesting features offered by the quantmod package is the possibility to build custom made indicators.

As in the previous post, we use the custom function read.prices that we introduced here. By default, this function reads the data in the “c:\\My Programs\\R\\Finance\\stocks\\
The contents of the folder are included in this zipped file (just remember to remove the fake *.doc extension before unzipping).

library(quantmod)

# read prices
source("c:\\My Programs\\R\\Finance\\read.prices_function.r")
data = read.prices()

# show the tickers
names(data)

## [1] "G.MI"   "IBE.MC" "BNP.PA" "ENI.MI" "BAYN.DE" "REP.MC" "BMW.DE"
## [8] "UG.PA"

# we choose BMW.DE for this example. Let'use only the adjusted prices since the
# beginning of 2012. The instruction "Ad" chooses the adjusted price only.

BMW = Ad(data$BMW.DE)["2012::"]

# Let's create a custom technical indicator that is the average of three simple
# moving averages and let's call it Av.TripleSMA. First we define a R custom 
# function that computes our indicator. We use simple moving averages (SMA)
# computed on 21, 42 and 63 days (roughly 1, 2 and 3 months):

Av.TripleSMA = function(x) {
ATS = (SMA(x, 21) + SMA(x, 42) + SMA(x, 63))/3
return(ATS)
}

# Then, we use the "newTA" instruction in order to make our indicator recognizable
# by quantmod. The "on = 1" argument tells quantmod to draw the indicator on the
# main plotting area of the chart (the same where the price is plotted).

custom.indicator = newTA(FUN = Av.TripleSMA, legend.name = "Av. Triple SMA", on = 1)

# Finally, we chart the price and the custom indicator with the following two
# instructions:

chartSeries(BMW, name = "BMW - Adj Prices", theme = chartTheme("white"))
custom.indicator()

21-1

The key instruction here is newTA that accepts custom built functions as arguments, and returns a function that can be called in chartSeries in the same way as the R built-in technical indicators. See ?newTA for further information.


Testing the Performance of a Custom Indicator in R

Learning R For Finance – Post 22

In the previous post we used the quantmod and the PerformanceAnalytics packages to build and plot an indicator on the BMW stock.

Once again we use the custom function read.prices.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
library(quantmod)
 
library(PerformanceAnalytics)
 
# read prices
source("c:\\My Programs\\R\\Finance\\read.prices_function.r")
data = read.prices()
 
# show the tickers
names(data)
 
## [1] "G.MI"   "IBE.MC" "BNP.PA" "ENI.MI" "BAYN.DE" "REP.MC" "BMW.DE"
## [8] "UG.PA"
 
# we choose BMW.DE for this example. Let'use only the adjusted prices since the beginning
# of 2012. The instruction "Ad" chooses the adjusted price only.
 
BMW = Ad(data$BMW.DE)["2012::"]
 
# let's compute the daily returns of BMW and of an hypothetical cash asset assuming that
# the latter always yields zero:
 
rets = na.omit(Return.calculate(BMW, method="discrete"))
rets$cash = 0
 
 
# Let's create a custom technical indicator that is the average of three simple moving
# averages. First we define a new function that computes our indicator.
 
Av.TripleSMA = function(x) {
ATS = (SMA(x, 21) + SMA(x, 42) + SMA(x, 63))/3
return(ATS)
}
 
# Let's apply the function to BMW prices:
 
AvTSMA = Av.TripleSMA(BMW)
 
# define a xts object called "weight" with the same class and dimension as  "BMW"
# and fill it all with NA:
 
weight = NA*BMW
 
# the investment rule is to buy when the price crosses the indicator upward and to
# stop the position going all cash when the price crosses the indicator downward
 
# these two instructions return TRUE when their condition is met:
weight.long = weight[((BMW > AvTSMA) & (lag(BMW,1) < lag(AvTSMA,1)))]
weight.stop.long = weight[((BMW < AvTSMA) & (lag(BMW,1) > lag(AvTSMA,1)))]
 
# these two instructions convert TRUE respectively to 1 (=100% weight) and 0
weight.long$BMW[is.na(weight.long$BMW)] = 1
weight.stop.long$BMW[is.na(weight.stop.long$BMW)] = 0
 
# these two instructions create a "weight" xts object that shows 100% weight in BMW
# or 100% weight in cash:
weight = rbind(weight.long$BMW, weight.stop.long$BMW)
weight$cash = 1 - weight$BMW
 
# we now backtest and plot the portfolio using the weights just calculated
 
rets.p = Return.portfolio(rets,weights = weight, geometric = TRUE, verbose = TRUE)
charts.PerformanceSummary(rets.p$returns, main = "Performance")

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# the following is an utility function to compute some portfolio statistics
showStats = function(rets,rf = 0,prob = 0.95) {
stats = rbind(table.AnnualizedReturns(rets,scale = 252,Rf = rf),
 + maxDrawdown(rets), CalmarRatio(rets, scale = 252),AdjustedSharpeRatio(rets, Rf = rf),
 + VaR(rets, p = prob, method = "historical"), ETL(rets, p = prob,   method = "historical"),
 + VaR(rets, p = prob, method = "modified"), ETL(rets,   p=prob, method = "modified"))
row.names(stats)[4] = "Max Drawdown"
row.names(stats)[5] = "Calmar Ratio"
row.names(stats)[6] = "Adjusted Sharpe"
row.names(stats)[7] = paste("VaR (historical) ","p=",prob,sep="")
row.names(stats)[8] = paste("Exp. Shortfall (historical) ","p=",prob,sep="")
row.names(stats)[9] = paste("VaR (modified) ","p=",prob,sep="")
row.names(stats)[10] = paste("Exp. Shortfall (modified) ","p=",prob,sep="")
return(round(stats, 3))
}
 
# display some portfolio performance (return and risk) indices:
 
showStats(rets.p$returns)
 
##                                   portfolio.returns
## Annualized Return                              0.075
## Annualized Std Dev                             0.169
## Annualized Sharpe (Rf=0%)                      0.444
## Max Drawdown                                   0.179
## Calmar Ratio                                   0.418
## Adjusted Sharpe                                0.488
## VaR (historical) p=0.95                       -0.015
## Exp. Shortfall (historical) p=0.95            -0.027
## VaR (modified) p=0.95                         -0.016
## Exp. Shortfall (modified) p=0.95              -0.023
 
# and the five worst drawdowns:
 
table.Drawdowns(rets.p$returns, top=5, digits=4)
 
##         From     Trough         To       Depth   Length  To Trough Recovery
## 1   2012-04-18 2012-10-10   2012-12-21  -0.1794   178       126       52
## 2   2013-12-03 2015-01-09   2015-01-23  -0.1398   299       289       10
## 3   2015-03-17 2015-09-18       <NA>    -0.1390   159       134       NA
## 4   2013-01-03 2013-04-26   2013-05-20  -0.0831    98       82        16
## 5   2013-05-21 2013-07-10   2013-09-10  -0.0750    81       37        44

The only relative difficulty in the listing above is the construction of the portfolio weights that depend on the investment rule that has been chosen . It should be noted that the weights in the PerformanceAnalytics package can only be positive. This means that the package cannot be used to directly backtest an investment rule that also requires short sales. We will see how to use R to test short sales in the future.

As a final note, it is also important to observe that  the price we have used for the analysis has been the adjusted close price. This means that risk indicators such as the maximum drawdown (MDD) may be slightly underestimated because this analysis does not take into account possible unfavourable infraday price movements (i.e. the adjusted low price would have been in most cases lower than the adjusted close price and therefore the infraday drawdown may have exceeded the end of day drawdown).